package edu.xhu.video.service.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import edu.xhu.api.behavior.BehaviorClient;
import edu.xhu.api.user.UserClient;
import edu.xhu.common.exception.CustomException;
import edu.xhu.model.behavior.dto.AddCommentDto;
import edu.xhu.model.biliuser.dtos.UserInfosDto;
import edu.xhu.model.behavior.dto.RootCommentDto;
import edu.xhu.model.behavior.dto.SonCommentDto;
import edu.xhu.model.behavior.pojos.BiliUserComment;
import edu.xhu.model.behavior.vo.CommentTree;
import edu.xhu.model.common.constants.CommentConstants;
import edu.xhu.model.common.dtos.ResponseResult;
import edu.xhu.model.common.enums.AppHttpCodeEnum;
import edu.xhu.video.service.VideoCommentService;
import edu.xhu.video.util.CommentTreeBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.stereotype.Service;
import org.springframework.data.redis.core.ZSetOperations;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 视频评论服务类
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class VideoCommentServiceImpl implements VideoCommentService {

    private final RedisTemplate redisTemplate;

    private final BehaviorClient behaviorClient;

    private final UserClient userClient;


    //双检加锁之锁对象，后期替换为分布式锁
    private final Object videoCommentHotZsetLock = new Object();
    private final Object videoCommentTimeZsetLock = new Object();


    /**
     * 获取评论树列表，每次查十条(10条根评论，并包装好了子评论)
     *
     * @param videoId  对应视频ID
     * @param offset   分页偏移量（已经获取到的评论树的数量）
     * @param sortTpye 排序类型 1 按热度排序 2 按时间排序
     * @return 评论树列表
     */
    @Override
    public ResponseResult<?> getCommentTreeByVideoId(Long videoId, Long offset, Integer sortTpye) {
//        long startApi = System.currentTimeMillis();

        Map<String, Object> resultMap = new HashMap<>(); //最终的结果集

        List<Object> pipelineResults = null; //用来保存redis管道命令的查询结果

        // 1.判断前端是否已经获取了全部根评论
        // 1.1. 先查询redis中的视频评论之zset
        String commentZsetKey = null;
        if (sortTpye == 1) {
            //根评论热度排序
            commentZsetKey = CommentConstants.VIDEO_COMMENT_ORDERBY_HOT_ZSET_KEY + videoId.toString();
        } else {
            //根评论时间排序
            commentZsetKey = CommentConstants.VIDEO_COMMENT_ORDERBY_TIME_ZSET_KEY + videoId.toString();
        }
        String finalVideoCommentZsetKey = commentZsetKey;
        //redis管道查询
        pipelineResults = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            connection.keyCommands().exists(finalVideoCommentZsetKey.getBytes());
            connection.zSetCommands().zCard(finalVideoCommentZsetKey.getBytes());
            //ZSet存在并且包含3个元素，返回的结果类似于：[true,3]
            //ZSet不存在，返回的结果类似于：[false,0]
            return null;
        });

        Long rootCommentCount = 0L; //该视频所有根评论的数量

        if (Boolean.parseBoolean(pipelineResults.get(0).toString()) == true) {
            //redis中缓存了该视频根评论的zset
            rootCommentCount = (Long) pipelineResults.get(1);
            redisTemplate.expire(finalVideoCommentZsetKey, 1, TimeUnit.DAYS); //刷新有效期，有效期为 1 天
        } else {
            //redis没有缓存该视频根评论的zset，双剑加锁走db手动刷新
            rootCommentCount = refreshVideoCommentZSetCache(videoId, sortTpye);
        }


        if (offset >= rootCommentCount) {
            // 表示前端已经获取到全部根评论了，没必要继续
            resultMap.put("more", false);
            resultMap.put("comments", Collections.emptyList());
            return ResponseResult.okResult(resultMap);
        } else if (offset + 10 >= rootCommentCount)
            // 表示这次查询会查完全部根评论
            resultMap.put("more", false);
        else
            // 表示这次查的只是冰山一角，还有很多评论没查到
            resultMap.put("more", true);

        // 2.前面步骤已经确保该视频根评论的zset存在于redis，下面开始查询构造评论之树
        List<CommentTree> commentTrees = buildCommentTreeByVideoId(videoId, offset, 10L, sortTpye);

        // 修剪 commentTrees
        commentTrees.forEach(commentTree -> {
            if (commentTree.getReplies() != null && commentTree.getReplies().size() > 3)
                // 保留索引为 0 到 2 的元素
                commentTree.setReplies(commentTree.getReplies().subList(0, 3));
        });

        resultMap.put("comments", commentTrees);


        //        System.out.println("rootCommentCount:" + rootCommentCount);
//        System.out.println("getCommentTreeByVideoId耗时:" + (System.currentTimeMillis() - startApi));
        return ResponseResult.okResult(resultMap);
    }

    /**
     * 返回某个根评论的评论树
     *
     * @param rootCommentId 根评论id
     * @return
     */
    @Override
    public ResponseResult<?> getMoreSonCommentsByRootId(Long rootCommentId) {

        // 1. 先查redis缓存
        String commentTreeJson = (String) redisTemplate.opsForValue().get(CommentConstants.VIDEO_ROOT_COMMENT_CACHE_KEY + rootCommentId.toString());

        // 命中缓存
        if (commentTreeJson != null && commentTreeJson.length() > 0) {
            CommentTree commentTree = JSONUtil.toBean(commentTreeJson, new TypeReference<CommentTree>() {
            }, true);
            if (commentTree != null)
                return ResponseResult.okResult(commentTree);
        }

        // 2. 未命中缓存，走DB，然后刷新缓存
        List<Long> rootCommentIds = new ArrayList<>();
        rootCommentIds.add(rootCommentId);

        List<CommentTree> commentTrees = getCommentTreeByRootCommentIds(rootCommentIds);

        CommentTree commentTree = null; //最终结果

        if (commentTrees != null && commentTrees.size() == 1) //一定只有一条根评论
            commentTree = commentTrees.get(0);

        //判空逻辑
        if (commentTree == null)
            return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR);

        // 3. 手动刷新缓存
        commentTreeJson = JSONUtil.toJsonStr(commentTree);
        redisTemplate.opsForValue().set(CommentConstants.VIDEO_ROOT_COMMENT_CACHE_KEY + rootCommentId.toString(),
                commentTreeJson,
                60,
                TimeUnit.SECONDS); //有效期60秒

        return ResponseResult.okResult(commentTree);
    }

    /**
     * 发表评论
     *
     * @param addCommentDto
     * @return 该评论对应的评论树
     */
    @Override
    public ResponseResult<?> addComment(AddCommentDto addCommentDto) {
        if (addCommentDto.getContent() == null || addCommentDto.getContent().length() == 0 || addCommentDto.getContent().length() > 2000)
            return ResponseResult.errorResult(500, "评论内容异常");

        ResponseResult<?> responseResult = null; //用来保存feign之调用结果
        List<Object> pipelineResults = null; //用来保存redis管道命令的查询结果
        BiliUserComment commentToInsert; //保存插入评论的所有数据
        CommentTree commentTree = null; //对应的评论树

        // 1. 远程调用打DB插入评论数据
        responseResult = behaviorClient.addComment(addCommentDto);
        if (!responseResult.getCode().equals(200)) {
            commentToInsert = null;
            return responseResult;
        } else {
            //插入评论成功，拿到插入评论的所有数据
            String json = responseResult.getData().toString();
            commentToInsert = JSONUtil.toBean(json, new TypeReference<BiliUserComment>() {
            }, true);
        }

        // 2.刷新评论缓存，重新构建评论树
        if (commentToInsert.getRootId() == 0L) {
            //commentToInsert是根评论

            // 2.1 刷新zset缓存
            // 拿到该视频两个zset之key
            String hotCommentZsetKey = CommentConstants.VIDEO_COMMENT_ORDERBY_HOT_ZSET_KEY + commentToInsert.getVideoId();
            String timeCommentZsetKey = CommentConstants.VIDEO_COMMENT_ORDERBY_TIME_ZSET_KEY + commentToInsert.getVideoId();

            // 走redis管道，刷新zset的有效期，顺便判断zset是否存在
            pipelineResults = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                connection.expire(hotCommentZsetKey.getBytes(), TimeUnit.DAYS.toSeconds(1));
                connection.expire(timeCommentZsetKey.getBytes(), TimeUnit.DAYS.toSeconds(1));
                return null;
            });

            // 如果redis中不存在对应的zset，走DB，否则直接往zset中添加元素
            if (Boolean.parseBoolean(pipelineResults.get(0).toString()) == false)
                //走db，刷新hotCommentZsetKey之zset缓存
                this.refreshVideoCommentZSetCache(commentToInsert.getVideoId(), 1);
            else
                redisTemplate.opsForZSet().add(hotCommentZsetKey, commentToInsert.getCommentId().toString(), 0);

            if (Boolean.parseBoolean(pipelineResults.get(0).toString()) == false)
                //走db，刷新timeCommentZsetKey之zset缓存
                this.refreshVideoCommentZSetCache(commentToInsert.getVideoId(), 2);
            else
                redisTemplate.opsForZSet().add(timeCommentZsetKey, commentToInsert.getCommentId().toString(), commentToInsert.getCreateTime().getTime());

            // 2.2 构建评论树
            commentTree = getCommentTreeFromCommentToInsert(commentToInsert);
        } else {
            //commentToInsert是子评论

            // 2.1 刷新zset缓存
            // 拿到该视频的热度zset
            String hotCommentZsetKey = CommentConstants.VIDEO_COMMENT_ORDERBY_HOT_ZSET_KEY + commentToInsert.getVideoId();

            // 走redis管道，刷新zset的有效期，顺便判断zset是否存在
            pipelineResults = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                connection.expire(hotCommentZsetKey.getBytes(), TimeUnit.DAYS.toSeconds(1));
                return null;
            });

            // 如果redis中不存在对应的zset，走DB，否则直接往zset中增加元素的分数值
            if (Boolean.parseBoolean(pipelineResults.get(0).toString()) == false)
                //走db，刷新hotCommentZsetKey之zset缓存
                this.refreshVideoCommentZSetCache(commentToInsert.getVideoId(), 1);
            else
                //增加分数
                redisTemplate.opsForZSet().incrementScore(hotCommentZsetKey, commentToInsert.getRootId().toString(), 1 * CommentConstants.COMMENT_WEIGHT);

            // 2.2 构建评论树
            commentTree = getCommentTreeFromCommentToInsert(commentToInsert);
        }

        if (commentTree == null)
            return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR);

        return ResponseResult.okResult(commentTree);
    }


    /**
     * 返回指定视频id的评论树(已经排好序)
     *
     * @param videoId  对应视频ID
     * @param offset   分页偏移量（已经获取到的评论树的数量）
     * @param pagesize 页面大小
     * @param sortTpye 排序类型 1 按热度排序 2 按时间排序
     * @return
     */
    private List<CommentTree> buildCommentTreeByVideoId(Long videoId, Long offset, Long pagesize, Integer sortTpye) {
        List<CommentTree> commentTrees = new ArrayList<>(); //最终结果集

        List<Object> pipelineResults = null; //用来保存redis管道命令的查询结果

        //2.开始构建树了
        //2.1 准备好zsetkey
        String commentZsetKey = null;
        if (sortTpye == 1) {
            //根评论热度排序
            commentZsetKey = CommentConstants.VIDEO_COMMENT_ORDERBY_HOT_ZSET_KEY + videoId.toString();
        } else {
            //根评论时间排序
            commentZsetKey = CommentConstants.VIDEO_COMMENT_ORDERBY_TIME_ZSET_KEY + videoId.toString();
        }
        String finalVideoCommentZsetKey = commentZsetKey;

        //2.2 查询指定索引范围的rootCommentId, 注意这个zset之api是闭区间索引
        Set<String> rootCommentIdsSet = redisTemplate.opsForZSet().reverseRange(finalVideoCommentZsetKey, offset, offset + pagesize - 1);

        //判空逻辑
        if (rootCommentIdsSet == null || rootCommentIdsSet.size() == 0)
            return Collections.emptyList();

        List<String> rootCommentIds = new ArrayList<>(rootCommentIdsSet);

        //2.3 去redis查询相应评论的缓存
        List<Long> uncachedRootCommentIds = new ArrayList<>(); //记录redis中没有缓存的根评论id
        List<Long> cachedRootCommentIds = new ArrayList<>(); //记录redis中缓存了的根评论id

        pipelineResults = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (String rootCommentId : rootCommentIds) {
                connection.get(redisTemplate.getKeySerializer().serialize(CommentConstants.VIDEO_ROOT_COMMENT_CACHE_KEY + rootCommentId));
            }
            return null;
        });

        for (int i = 0; i < rootCommentIds.size(); i++) {
            if (pipelineResults.get(i) == null || pipelineResults.get(i).toString().length() == 0)
                //未命中缓存
                uncachedRootCommentIds.add(Long.valueOf(rootCommentIds.get(i)));
            else {
                //命中缓存
                String commentTreeJson = pipelineResults.get(i).toString();
                CommentTree commentTree = JSONUtil.toBean(commentTreeJson, new TypeReference<CommentTree>() {
                }, true);

                //添加至最终结果集
                commentTrees.add(commentTree);

                cachedRootCommentIds.add(Long.valueOf(rootCommentIds.get(i)));
            }
        }

        //2.4 如果有未命中缓存的根评论，走DB，拿到uncachedRootCommentIds的评论树
        List<CommentTree> uncachedCommentTree = null;
        if (uncachedRootCommentIds != null && uncachedRootCommentIds.size() != 0)
            uncachedCommentTree = getCommentTreeByRootCommentIds(uncachedRootCommentIds);

        //2.5 刷新uncachedRootCommentIds的缓存和有效期，刷新cachedRootCommentIds的有效期
        //走redis管道
        List<CommentTree> finalUncachedCommentTree = uncachedCommentTree;
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            //刷新 uncachedRootCommentIds 的缓存和有效期
            if (finalUncachedCommentTree != null && finalUncachedCommentTree.size() != 0)
                for (CommentTree commentTree : finalUncachedCommentTree) {
                    byte[] key = redisTemplate.getKeySerializer().serialize(
                            CommentConstants.VIDEO_ROOT_COMMENT_CACHE_KEY + commentTree.getCommentId().toString());
                    byte[] value = redisTemplate.getValueSerializer().serialize(JSONUtil.toJsonStr(commentTree));
                    connection.set(key, value);
                    connection.expire(key, 60); // 过期时间为 60 秒。
                }

            //刷新 cachedRootCommentIds 的有效期
            if (cachedRootCommentIds != null && cachedRootCommentIds.size() != 0)
                for (Long commentId : cachedRootCommentIds) {
                    byte[] key = redisTemplate.getKeySerializer().serialize(
                            CommentConstants.VIDEO_ROOT_COMMENT_CACHE_KEY + commentId.toString());
                    connection.expire(key, 60); // 过期时间为 60 秒。
                }
            return null;
        });

        //2.6 封装 uncachedRootCommentIds 的评论树至最终结果集
        if (uncachedCommentTree != null && uncachedCommentTree.size() != 0)
            commentTrees.addAll(uncachedCommentTree);

        //3. 对commentTrees中的根评论拉通排序一下
        //根据热度/时间排序
        if (sortTpye == 1)
            Collections.sort(commentTrees, Comparator.comparingDouble(CommentTree::getScore).reversed());
        else
            Collections.sort(commentTrees, Comparator.comparing(CommentTree::getCreateTime).reversed());

//        System.out.println(pipelineResults);
//        System.out.println(pipelineResults.size());

//        for (String rootCommentId : rootCommentIds)
//            System.out.println(rootCommentId);
//        System.out.println(rootCommentIds.size());

        return commentTrees;
    }

    /**
     * 走DB，返回rootCommentIds的评论之树，根评论无顺序，子评论有顺序
     *
     * @param rootCommentIds
     * @return 返回rootCommentIds的评论之树，
     */
    private List<CommentTree> getCommentTreeByRootCommentIds(List<Long> rootCommentIds) {
        ResponseResult<?> responseResult = null; //用来保存feign之调用结果

        List<Map> rootCommentMaplist = null; //根评论之map列表, bili_user_comment表中的记录map
        List<Map> sonCommentMaplist = null; //子评论之map列表，时间顺序，bili_user_comment表中的记录map
        List<Map> userInfoMapList = null; //用户全部信息之map列表

        List<CommentTree> commentTrees; // rootCommentIds的根评论之树

        if (rootCommentIds != null && rootCommentIds.size() != 0) {

            //1. 拿到根评论之记录map，包含热度值
            RootCommentDto rootCommentDto = new RootCommentDto();
            rootCommentDto.setLoveWeight(CommentConstants.LOVE_WEIGHT)
                    .setCommentWeight(CommentConstants.COMMENT_WEIGHT)
                    .setCommentIds(rootCommentIds);

            responseResult = behaviorClient.queryRootCommentsByCommentIds(rootCommentDto);
            if (!responseResult.getCode().equals(200)) {
                throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
            }
            JSONArray jsonArray = JSONUtil.parseArray(responseResult.getData());
            rootCommentMaplist = JSONUtil.toList(jsonArray, Map.class);

            //2. 拿到所有uncachedRootCommentIds的子评论，时间顺序
            SonCommentDto sonCommentDto = new SonCommentDto();
            sonCommentDto.setRootCommentIds(rootCommentIds);

            responseResult = behaviorClient.querySonCommentsByRootIds(sonCommentDto);
            if (!responseResult.getCode().equals(200)) {
                throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
            }

            JSONArray sonArray = JSONUtil.parseArray(responseResult.getData());
            sonCommentMaplist = JSONUtil.toList(sonArray, Map.class);

            //3. 拿到所有评论（根评论 + 子评论）的用户信息
            //获取用户的id
            Set<Long> userIds = new HashSet<>();
            rootCommentMaplist.forEach(item -> {
                if (item.containsKey("user_id"))
                    userIds.add((Long) item.get("user_id"));
            });

            sonCommentMaplist.forEach(item -> {
                if (item.containsKey("user_id"))
                    userIds.add((Long) item.get("user_id"));
                if (item.containsKey("to_user_id"))
                    userIds.add((Long) item.get("to_user_id"));
            });

            //根据用户ids获取全部用户的信息
            UserInfosDto commentUserInfosDto = new UserInfosDto();
            commentUserInfosDto.setUserIds(new ArrayList<Long>(userIds));

            responseResult = userClient.queryAllUserInfoByIds(commentUserInfosDto);
            if (!responseResult.getCode().equals(200)) {
                throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
            }

            JSONArray userInfoJsonArray = JSONUtil.parseArray(responseResult.getData());
            userInfoMapList = JSONUtil.toList(userInfoJsonArray, Map.class);

            //5. 开始构建List<Long> rootCommentIds的评论树
            commentTrees = CommentTreeBuilder.buildCommentTree(
                    rootCommentMaplist,
                    sonCommentMaplist,
                    userInfoMapList,
                    null); //这里的根评论排序类型不重要

            //判空逻辑
            if (commentTrees == null)
                commentTrees = new ArrayList<>();

        } else {
            commentTrees = new ArrayList<>();
        }

        return commentTrees;
    }

    /**
     * 双剑加锁，刷新视频评论的ZSet缓存
     *
     * @return 该视频根评论的数量
     */
    private Long refreshVideoCommentZSetCache(Long videoId, Integer sortTpye) {
        Long rootCommentCount = 0L; //该视频所有根评论的数量

        List<Object> pipelineResults = null; //用来保存redis管道命令的查询结果

        //后期需要替换为分布式锁
        if (sortTpye == 1)
            synchronized (videoCommentHotZsetLock) {
                String finalCommentZsetKey = CommentConstants.VIDEO_COMMENT_ORDERBY_HOT_ZSET_KEY + videoId.toString();
                //再查一次缓存
                //redis管道查询
                pipelineResults = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                    connection.keyCommands().exists(finalCommentZsetKey.getBytes());
                    connection.zSetCommands().zCard(finalCommentZsetKey.getBytes());
                    return null;
                });

                if (Boolean.parseBoolean(pipelineResults.get(0).toString()) == true) {
                    //redis中缓存了该视频根评论的zset
                    rootCommentCount = (Long) pipelineResults.get(1);
                    redisTemplate.expire(finalCommentZsetKey, 1, TimeUnit.DAYS); //刷新有效期，有效期为 1 天
                } else {
                    System.out.println("refreshVideoCommentZSetCache打到DB了");
                    //redis没有缓存该视频根评论的zset，走db手动刷新
                    List<Map> rootCommentMaplist = null; //根评论之map列表

                    List<Long> videoIds = new ArrayList<>();
                    videoIds.add(videoId);
                    //拿到视频的所有根评论id，热度顺序
                    RootCommentDto rootCommentDto = new RootCommentDto();
                    rootCommentDto.setVideoIds(videoIds)
                            .setLoveWeight(CommentConstants.LOVE_WEIGHT)
                            .setCommentWeight(CommentConstants.COMMENT_WEIGHT)
                            .setSortType(CommentConstants.ROOT_COMMENT_SORT_BY_HOTNESS);

                    ResponseResult<?> responseResult = behaviorClient.queryRootCommentIdsByVideoId(rootCommentDto);
                    if (!responseResult.getCode().equals(200)) {
                        throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
                    }
                    JSONArray jsonArray = JSONUtil.parseArray(responseResult.getData());
                    rootCommentMaplist = JSONUtil.toList(jsonArray, Map.class);

                    rootCommentCount = (long) rootCommentMaplist.size();

                    //刷新redis缓存,批量添加
                    Set<ZSetOperations.TypedTuple<Object>> zsetValues = new HashSet<>();
                    rootCommentMaplist.forEach(rootCommentMap -> {
                        // 添加元素及其分数
                        String value = rootCommentMap.get("comment_id").toString();
                        Double score = Double.parseDouble(rootCommentMap.get("score").toString());
                        zsetValues.add(new DefaultTypedTuple<>(value, score));
                    });
                    if (zsetValues != null && zsetValues.size() != 0) {
                        redisTemplate.opsForZSet().add(finalCommentZsetKey, zsetValues);
                        redisTemplate.expire(finalCommentZsetKey, 1, TimeUnit.DAYS); //有效期为 1 天
                    } else {
                        //创建一个空的zset并设置其过期时间
                        //先添加一个临时元素，然后再删除这个元素
                        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                            // 创建一个临时元素
                            String tempElement = "tempElement";
                            Double score = 0.0;
                            // 添加临时元素到zset
                            connection.zAdd(finalCommentZsetKey.getBytes(), score, tempElement.getBytes());
                            // 设置过期时间为1天
                            connection.expire(finalCommentZsetKey.getBytes(), TimeUnit.DAYS.toSeconds(1));
                            // 删除临时元素
                            connection.zRem(finalCommentZsetKey.getBytes(), tempElement.getBytes());
                            return null;
                        });
                    }
                }
            }
        else
            synchronized (videoCommentTimeZsetLock) {
                String finalCommentZsetKey = CommentConstants.VIDEO_COMMENT_ORDERBY_TIME_ZSET_KEY + videoId.toString();
                //再查一次缓存
                //redis管道查询
                pipelineResults = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                    connection.keyCommands().exists(finalCommentZsetKey.getBytes());
                    connection.zSetCommands().zCard(finalCommentZsetKey.getBytes());
                    return null;
                });

                if (Boolean.parseBoolean(pipelineResults.get(0).toString()) == true) {
                    //redis中缓存了该视频根评论的zset
                    rootCommentCount = (Long) pipelineResults.get(1);
                    redisTemplate.expire(finalCommentZsetKey, 1, TimeUnit.DAYS); //刷新有效期，有效期为 1 天
                } else {
                    System.out.println("refreshVideoCommentZSetCache打到DB了");
                    //redis没有缓存该视频根评论的zset，走db手动刷新
                    List<Map> rootCommentMaplist = null; //根评论之map列表

                    List<Long> videoIds = new ArrayList<>();
                    videoIds.add(videoId);
                    //拿到视频的所有根评论id，时间顺序
                    RootCommentDto rootCommentDto = new RootCommentDto();
                    rootCommentDto.setVideoIds(videoIds)
                            .setLoveWeight(CommentConstants.LOVE_WEIGHT)
                            .setCommentWeight(CommentConstants.COMMENT_WEIGHT)
                            .setSortType(CommentConstants.ROOT_COMMENT_SORT_BY_TIME);

                    ResponseResult<?> responseResult = behaviorClient.queryRootCommentIdsByVideoId(rootCommentDto);
                    if (!responseResult.getCode().equals(200)) {
                        throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
                    }
                    JSONArray jsonArray = JSONUtil.parseArray(responseResult.getData());
                    rootCommentMaplist = JSONUtil.toList(jsonArray, Map.class);

                    rootCommentCount = (long) rootCommentMaplist.size();

                    //刷新redis缓存,批量添加
                    Set<ZSetOperations.TypedTuple<Object>> zsetValues = new HashSet<>();
                    rootCommentMaplist.forEach(rootCommentMap -> {
                        // 添加元素及其分数
                        String value = rootCommentMap.get("comment_id").toString();
                        Double score = Double.parseDouble(rootCommentMap.get("create_time").toString());
                        zsetValues.add(new DefaultTypedTuple<>(value, score));
                    });

                    if (zsetValues != null && zsetValues.size() != 0) {
                        redisTemplate.opsForZSet().add(finalCommentZsetKey, zsetValues);
                        redisTemplate.expire(finalCommentZsetKey, 1, TimeUnit.DAYS); //有效期为 1 天
                    } else {
                        //创建一个空的zset并设置其过期时间
                        //先添加一个临时元素，然后再删除这个元素
                        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                            // 创建一个临时元素
                            String tempElement = "tempElement";
                            Double score = 0.0;
                            // 添加临时元素到zset
                            connection.zAdd(finalCommentZsetKey.getBytes(), score, tempElement.getBytes());
                            // 设置过期时间为1天
                            connection.expire(finalCommentZsetKey.getBytes(), TimeUnit.DAYS.toSeconds(1));
                            // 删除临时元素
                            connection.zRem(finalCommentZsetKey.getBytes(), tempElement.getBytes());
                            return null;
                        });
                    }
                }
            }

        return rootCommentCount;
    }


    /**
     * 根据commentToInsert获取完整的commentTree，顺便刷新redis缓存
     *
     * @param commentToInsert 插入的评论，根评论或者子评论
     * @return 一棵完整的根评论树
     */
    private CommentTree getCommentTreeFromCommentToInsert(BiliUserComment commentToInsert) {
        ResponseResult<?> responseResult = null; //用来保存feign之调用结果
        CommentTree commentTree = new CommentTree(); //对应的评论树

        if (commentToInsert.getRootId() == 0L) {
            //插入评论是根评论
            // 1.复制属性
            BeanUtils.copyProperties(commentToInsert, commentTree);

            // 2.打db，组装用户信息
            List<Map> userInfoMapList = null; //用户全部信息之map列表
            //获取用户的id
            Set<Long> userIds = new HashSet<>();
            userIds.add(commentToInsert.getUserId());
            //根据用户ids获取全部用户的信息
            UserInfosDto commentUserInfosDto = new UserInfosDto().setUserIds(new ArrayList<Long>(userIds));
            responseResult = userClient.queryAllUserInfoByIds(commentUserInfosDto);
            if (!responseResult.getCode().equals(200)) {
                throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
            }
            JSONArray userInfoJsonArray = JSONUtil.parseArray(responseResult.getData());
            userInfoMapList = JSONUtil.toList(userInfoJsonArray, Map.class);
            if (userInfoMapList.size() > 0)
                commentTree.setUserAllInfo(userInfoMapList.get(0));

            //            // 3. 将该评论树刷新至redis缓存，60秒有效期
            //            redisTemplate.opsForValue().set(
            //                    CommentConstants.VIDEO_ROOT_COMMENT_CACHE_KEY + commentTree.getCommentId().toString(),
            //                    JSONUtil.toJsonStr(commentTree),
            //                    60, TimeUnit.SECONDS);
        } else {
            //插入评论是子评论
            // 1. 走DB拿最新的数据
            List<Long> rootCommentIds = new ArrayList<>();
            rootCommentIds.add(commentToInsert.getRootId());
            List<CommentTree> commentTrees = getCommentTreeByRootCommentIds(rootCommentIds);

            if (commentTrees == null || commentTrees.size() != 1)
                return null;
            else
                commentTree = commentTrees.get(0);

            // 2. 将该评论树刷新至redis缓存，60秒有效期
            redisTemplate.opsForValue().set(
                    CommentConstants.VIDEO_ROOT_COMMENT_CACHE_KEY + commentTree.getCommentId().toString(),
                    JSONUtil.toJsonStr(commentTree),
                    60, TimeUnit.SECONDS);
        }

        return commentTree;
    }

}
